﻿using HslCommunication;
using HslCommunication.Profinet.Siemens;
using LongSCADA_WPF.Models;
using LongSCADA_WPF.ViewModels;
using Masuit.Tools;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MiniExcelLibs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LongSCADA_WPF.Helpers
{
    public class GlobalHelper : IDisposable
    {
        public bool IsPLCConnected { get; set; }
        public SiemensS7Net Plc { get; set; }
        /// <summary>
        /// 存取所有读取到的PLC数据
        /// </summary>
        public ConcurrentDictionary<string, object> ReadDataDic { get; set; } = new();
        public List<ReadEntity> ReadEntityList { get; set; } = new();
        public List<WriteEntity> WriteEntityList { get; set; } = new();

        private ConcurrentQueue<ScadaReadData> _scadaReadDataQueue = new();

        private readonly IOptionsSnapshot<RootParam> _options;
        private readonly ILogger<MainViewModel> _logger;
        private CancellationTokenSource _cts = new();
        private CancellationTokenSource _ctsSave = new();
        public GlobalHelper(IOptionsSnapshot<RootParam> options, ILogger<MainViewModel> logger)
        {
            _options = options;
            _logger = logger;
            InitPlc();
            InitExcelsAddress();
            InitDequeue();
           
        }

        /// <summary>
        /// 初始化PLC连接
        /// </summary>
        /// <returns></returns>
        public async Task InitPlcConnectionAsync()
        {
            //开发外部，单例注入时使用
            try
            {
                HslCommunication.OperateResult operateResult = await Plc.ConnectServerAsync();
                IsPLCConnected = operateResult.IsSuccess;
                if (!operateResult.IsSuccess)
                {
                    _logger.LogError($"PLC 连接失败:{_options.Value.PlcParam.PlcIp}:{_options.Value.PlcParam.PlcPort}");
                }
            }
            catch (Exception ex)
            {
                IsPLCConnected = false;
                _logger.LogError($"PLC 连接异常 {ex.Message}");
            }
        }
        /// <summary>
        ///  开始读取PLC数据，并存入ReadDataDic
        /// </summary>
        /// <param name="externalToken"></param>
        /// <returns></returns>
        public  Task StartReadDataAsync(CancellationToken? externalToken = null)
        {
            StopReadData();
            //创建令牌
            _cts = CancellationTokenSource.CreateLinkedTokenSource(externalToken?? CancellationToken.None);

            return Task.Run(async () =>
            {
                while (!_cts.Token.IsCancellationRequested)
                {
                    try
                    {
                        //批量读取给字典,对应三种类型读取方式
                        await UpdateControlData();
                        await UpdateMonitorData();
                        await UpdateProcessData();

                        await ReadSaveAsync();

                        await Task.Delay(_options.Value.PlcParam.PlcCycleInterval, _cts.Token);
                    }
                    catch ( Exception ex)
                    {

                        _logger.LogError($"读取异常{ex.Message}");
                    }
                }
            });
        }

        /// <summary>
        /// 生产者消费者模型--生产者（写入数据队列：PLC->数据队列）
        /// </summary>
        /// <returns></returns>
        private async Task ReadSaveAsync()
        {
            List<ReadEntity> saveAddress = ReadEntityList.Where(x => x.Save).ToList();
            if (!saveAddress.Any()) return;

            //创建新的数据对象
            ScadaReadData scadaReadData = new ScadaReadData()
            {
                CreateTime = DateTime.Now,
                UpdateTime = DateTime.Now
            };
            // 反射获取数据
            foreach (ReadEntity address in saveAddress)
            {
                System.Reflection.PropertyInfo? property = typeof(ScadaReadData).GetProperty(address.En);

                if (property != null && ReadDataDic.TryGetValue(address.En,out var value))
                {
                    property.SetValue(scadaReadData, value);
                }
            }
            //数据入队
            _scadaReadDataQueue.Enqueue(scadaReadData);
        }


        /// <summary>
        /// 生产者与消费者模型--消费者（出数据队列，数据队列-->数据库）
        /// </summary>
        private void InitDequeue()
        {
            Task.Run(async () =>
            {
                while (!_ctsSave.Token.IsCancellationRequested)
                {
                    try
                    {
                        if (_scadaReadDataQueue.TryDequeue(out var data))
                        {
                            //写入数据库
                            await SqlSugarHelper.Db.Insertable(data).ExecuteCommandAsync();
                        }
                        else
                        {
                            await Task.Delay(100, _ctsSave.Token);
                        }
                    }
                    catch (Exception ex)
                    {

                        Debug.WriteLine(ex.Message);
                    }
                }
            },_ctsSave.Token);
        }

        private async Task UpdateProcessData()
        {
            await UpdatePlcToReadDataDic<float>("Data", "DBD");
        }

        private async Task UpdateMonitorData()
        {
            await UpdatePlcToReadDataDic<bool>("Monitor", "DBX");
        }

        private async Task UpdateControlData()
        {
           await UpdatePlcToReadDataDic<bool>("Control", "DBX");
        }
        /// <summary>
        /// 读取 PLC 数据，并保存到字典<EN,Value>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="module"></param>
        /// <param name="addressType"></param>
        /// <returns></returns>
        private async Task UpdatePlcToReadDataDic<T>(string module,string addressType)
        {
            try
            {
                List<ReadEntity> addressList = ReadEntityList.Where(x => x.Module == module && x.Address.Contains(addressType)).ToList();

                if (addressList.Count < 1)
                {
                    return;
                }

                //批量读取结果
                OperateResult<T[]>? result;
                if (typeof(T) == typeof(bool))
                {
                    result = await Plc.ReadBoolAsync(addressList.First().Address, (ushort)addressList.Count)
                        as OperateResult<T[]>;
                }
                else if (typeof(T) == typeof(float))
                {
                    result = await Plc.ReadFloatAsync(addressList.First().Address, (ushort)addressList.Count)
                        as OperateResult<T[]>;
                }
                else
                {
                    _logger.LogError($"暂不支持类型:{typeof(T)}");
                    return;
                }

                //将 result 结果放到数据字典中
                if (!result.IsSuccess)
                {
                    _logger.LogError("数据读取失败");
                    return;
                }
                for (int i = 0; i < addressList.Count; i++)
                {
                    if (ReadDataDic.ContainsKey(addressList[i].En))
                    {
                        //更新
                        ReadDataDic[addressList[i].En] = result.Content[i];
                    }
                    else
                    {
                        //添加
                        ReadDataDic.TryAdd(addressList[i].En, result.Content[i]);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }

        /// <summary>
        /// 停止读取PLC数据
        /// </summary>
        /// <param name="externalToken"></param>
        public void StopReadData(CancellationToken? externalToken = null)
        {
            try
            {
               if (_cts!= null)
               {
                    _cts.Cancel();
                    _cts.Dispose();
               }
            }
            catch (Exception ex)
            {
                _logger.LogError($"停止读取异常{ex.Message}");
            }
        }

        /// <summary>
        /// 停止保存数据到数据库
        /// </summary>
        /// <param name="externalToken"></param>
        public void StopSave(CancellationToken? externalToken = null)
        {
            try
            {
               if (_ctsSave!= null)
               {
                    _ctsSave.Cancel();
                    _ctsSave.Dispose();
               }
            }
            catch (Exception ex)
            {
                _logger.LogError($"停止读取异常{ex.Message}");
            }
        }

        /// <summary>
        /// 实时获取字典数值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T GetValue<T>(string key)
        {
            if (ReadDataDic.TryGetValue(key, out object value))
            {
                return (T)value;
            }
            return default;
        }

        private void InitExcelsAddress()
        {
            string rootPath = AppDomain.CurrentDomain.BaseDirectory + "Configs\\";
            string readPath = rootPath + "AddressRead.xlsx";
            string writePath = rootPath + "AddressWrite.xlsx";
            if (!File.Exists(readPath) || !File.Exists(writePath))
            {
                _logger.LogError($"找不到读-{readPath}-或写{writePath}的配置文件");
                return;
            }
            try
            {
                ReadEntityList = MiniExcel.Query<ReadEntity>(readPath)
                    .Where(x => !string.IsNullOrEmpty(x.Address)) //PLC地址不为空
                    .ToList();

                WriteEntityList = MiniExcel.Query<WriteEntity>(writePath)
                    .Where(x => !string.IsNullOrEmpty(x.Address))
                    .ToList();
            }
            catch (Exception ex)
            {

                _logger.LogError($"MiniExcel 读取异常{ex.Message}");
            }
        }

        private void InitPlc()
        {
            Plc = new SiemensS7Net(_options.Value.PlcParam.PlcType, _options.Value.PlcParam.PlcIp);
            Plc.Port= _options.Value.PlcParam.PlcPort;
            Plc.Rack = _options.Value.PlcParam.PlcRack;
            Plc.Slot = _options.Value.PlcParam.PlcSlot;
            Plc.ConnectTimeOut = _options.Value.PlcParam.PlcConnectTimeOut;
        }

        public void Dispose()
        {
           
        }
    }
}
